#include <iostream>

class Foo {
public:
  Foo() {
    std::cout << "Constructor"
              << "\n";
  }

  ~Foo() {
    std::cout << "Destructor"
              << "\n";
  }

  Foo(const Foo &) {
    std::cout << "Copy Constructor"
              << "\n";
  }

  Foo(Foo &&) {
    std::cout << "Move Constructor"
              << "\n";
  }

  Foo &operator=(const Foo &) {
    std::cout << "Copy Assignment"
              << "\n";
    return *this;
  }

  Foo &operator=(Foo &&) {
    std::cout << "Move Assignment"
              << "\n";

    return *this;
  }
};

// Return Value Optimization
Foo FooRVO() { return Foo(); }
// Named RVO
Foo FooNRVO() {
  Foo foo;
  return foo;
}
// Copy Elision
void CopyElision(Foo foo) {}
// Return a Global
const Foo foo;

Foo ReturnGlobal() { return foo; }
// Return a Parameter
Foo ReturnParam(Foo foo) { return foo; }
// Runtime Decision
Foo FooRVO(bool is_x) { return is_x ? Foo() : Foo(); }
Foo RVOButNotNRVO(bool is_x) {
  Foo x;
  return is_x ? x : Foo();
}
Foo FooNoNRVO(bool is_x) {
  Foo x, y;
  return is_x ? x : y;
}

// Return by std::move
Foo FooMove() { return std::move(Foo()); }
Foo FooMoveNRVO() {
  Foo foo;
  return std::move(foo);
}
// Return a Member
struct Bar {
  Foo foo;
};

Foo ReturnMember() { return Bar().foo; }

int main(int argc, char *argv[]) {
  // Foo f1 = FooRVO(); // 1 ctor
  // Foo f2 = FooNRVO(); // 1 ctor
  // CopyElision(Foo()); // 1 ctor
  // Foo f3 = ReturnGlobal(); // 1 ctor 1 copy
  // Foo f4 = ReturnParam(Foo()); // 2 ctor 1 move
  // Foo f5 = FooRVO(true); // 2 ctor
  // Foo f6 = RVOButNotNRVO(false); // 3 ctor
  // Foo f7 = FooNoNRVO(true); // 3 ctor 1 copy
  // Foo f8 = FooMove(); // 2 ctor 1 move
  // Foo f9 = FooMoveNRVO(); // 2 ctor 1 move
  Foo f10 = ReturnMember(); // 2 ctor 1 move
  return 0;
}
